using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Xml;
using System.Collections.Generic;
using Ltf.Remoting.RemoteEvents;
using Ltf.Remoting.TFAccess;
using Ltf.Remoting.TeamFoundation;
using System.Reflection;

namespace Ltf.Remoting.Commands
{
	/// <summary>
	/// Summary description for Class1.
	/// </summary>
	public class DirDiffer
	{
		[DllImport("imagehlp.dll")]
		public static extern int MapFileAndCheckSumA(string fileName, ref int headerSum, ref int checkSum);

		private IDifRepository repo1, repo2;
		private string path1, path2;
		private XmlDocument result;
		private ArrayList errors;
		private string switches;
		private bool useIgnoreList = false;

		private int _counter = 0;
		private string tempPath;

		private bool checkSumsOnly = false;

		private Dictionary<string, string> ignoreList = new Dictionary<string, string>();


		public event EventHandler DiffAnotherTen;

		public event EventHandler ProgressClick;

		public event EventHandler Finished;


		public DirDiffer(IDifRepository repo1, IDifRepository repo2, string path1, string path2, Dictionary<string, string> ignoreList)
		{
			this.repo1 = repo1;
			this.repo2 = repo2;
			this.path1 = path1;
			this.path2 = path2;

			tempPath = ServerConnections.GetConnectionForPath(path1).Settings.TempPath;
			this.ignoreList = ignoreList;
			useIgnoreList = true;
			checkSumsOnly = true;
		}

		public DirDiffer(IDifRepository repo1, IDifRepository repo2, string path1, string path2, bool ignoreTabExpansion, bool ignoreSpaceChanges,
									bool ignoreWhiteSpace, bool ignoreBlankLines, bool ignoreCase)
		{
			this.repo1 = repo1;
			this.repo2 = repo2;
			this.path1 = path1;
			this.path2 = path2;

			switches = (ignoreTabExpansion ? " -E" : "") + (ignoreSpaceChanges ? " -b" : "") + (ignoreWhiteSpace ? " -w" : "")
				+ (ignoreBlankLines ? " -B" : "") + (ignoreCase ? " -i" : "");

			tempPath = ServerConnections.GetConnectionForPath(path1).Settings.TempPath;
			useIgnoreList = false;
			checkSumsOnly = false;
		}

		public bool Cancelled;

		public void BeginDiffs()
		{
			Cancelled = false;
			GoDiffs(useIgnoreList);
			if (Finished != null)
				Finished(this, new EventArgs());
		}

		public XmlDocument GoDiffs(bool useIgnoreList)
		{
			errors = new ArrayList();
			if (!repo1.FolderExists(path1))
			{
				errors.Add("No such directory -" + path1);
				result = null;
				return result;
			}
			if (!repo2.FolderExists(path2))
			{
				errors.Add("No such directory -" + path2);
				result = null;
				return result;
			}

			// start the diffing!
			result = new XmlDocument();
			result.LoadXml(string.Format("<DiffResults Path1='{0}' Path2='{1}' />",path1, path2));
			int NodeIndex = 1;
			bool cancel = false;
			RecurseDiff(path1, result.FirstChild, useIgnoreList, ref NodeIndex, ref cancel);
			Cancelled = cancel;
			return result;
		}


		private bool RecurseDiff(String path, XmlNode ResultsNode, bool useIgnoreList, ref int nodeIndex, ref bool cancel)
		{
			bool result = false;
			List<string> processedFiles = new List<string>();
			TFServerConnection connection = ServerConnections.GetConnectionForPath(path);
			foreach (string fn in repo1.GetFolderItems(path,DifItemType.File)) // something here to include all bp6 source files
			{

				if (connection.Settings.IsFilePathIncluded(fn))
				{
					string fpn = fn.Substring(path1.Length);
					string dfn = path2 + fpn;
					string nodename;

					if (repo2.FileExists(dfn))
					{
						if (!useIgnoreList || !ignoreList.ContainsKey(fn))
						{

							if (!checkSumsOnly)
							{
								int hs1 = 0, cs1 = 0;
								int hs2 = 0, cs2 = 0;
								try
								{
									using (Stream s1 = repo1.GetFileItemStream(fn))
									{
										using (Stream s2 = repo2.GetFileItemStream(dfn))
										{
											if (s2 == null)
											{
												nodename = "FileMissing";
												result = true;
											}
											else
											{

												using (StreamReader sr1 = new StreamReader(s1))
												{
													using (StreamReader sr2 = new StreamReader(s2))
													{
														string tempfile1 = string.Format("{0}/{1}.1", tempPath, Path.GetFileName(fn));
														string tempfile2 = string.Format("{0}/{1}.2", tempPath, Path.GetFileName(dfn));
														File.WriteAllText(tempfile1, sr1.ReadToEnd());
														File.WriteAllText(tempfile2, sr2.ReadToEnd());

														int fh1 = MapFileAndCheckSumA(tempfile1, ref hs1, ref cs1);

														int fh2 = MapFileAndCheckSumA(tempfile2, ref hs2, ref cs2);
														string args = "\"" + tempfile1 + "\" \"" + tempfile2 + "\" " + switches;
														if (cs1 != cs2) // if the checksums don't match then the files are different
														{
															Process diff = new Process();

															// they failed the checksum test, but its worth checking with diff as case/blanks etc aren't handled by cs.
															ProcessStartInfo psi = new ProcessStartInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\gnu\\diff.exe", args);
															psi.RedirectStandardOutput = true;
															psi.RedirectStandardError = true;
															psi.UseShellExecute = false;
															diff.StartInfo = psi;
															diff.StartInfo.CreateNoWindow = true;
															try
															{

																diff.Start();
																int difflen = diff.StandardOutput.ReadToEnd().Length;
																string errors = diff.StandardError.ReadToEnd();
																nodename = (difflen > 0) ? "FileDiff" : "FileSame"; //they might not be different if ignoring case/blanks etc.
															}
															catch
															{
																nodename = "FileDiff";
																result = true;
															}

														}
														else
														{
															nodename = "FileSame";
														}
														try
														{
															File.Delete(tempfile1);
															File.Delete(tempfile2);
														}
														catch
														{
														}
													}
												}
											}
										}

									}
								}
								catch
								{
									nodename = "FileError";
								}
							}
							else
							{
								int repo1cs=repo1.GetFileChecksum(fn);
								int repo2cs=repo2.GetFileChecksum(dfn);
								if (repo1cs == repo2cs)
								{
									nodename = "FileSame";
								}
								else if (repo1cs == -1)
								{
									nodename = "FileAdded";
								}
								else if (repo2cs == -1)
								{
									nodename = "FileMissing";
								}
								else
								{
									nodename = "FileDiff";
									result = true;
								}
							}
						}
						else
						{
							nodename = "FileMatches";
						}
					}
					else
					{
						nodename = "FileMissing";
						result = true;
					}

					if (nodename == "FileDiff" && repo2.LastItemHasPotentialConflict)
						nodename = "FileConflict";

					nodeIndex = AddFileNode(ResultsNode, nodeIndex, fpn, nodename);
					if (ProgressClick != null)
					{
						ProgressIndicatorEventArgs ea = new ProgressIndicatorEventArgs(fpn);
						ProgressClick(this, ea);
						cancel = ea.Cancel;
						if (cancel)
							return result;
					}
					if (_counter++ == 10)
					{
						_counter = 0;
						if (DiffAnotherTen != null) DiffAnotherTen(this, new EventArgs());
					}

				}
				processedFiles.Add(Path.GetFileName(fn));
			}
			string afpn = path.Substring(path1.Length);
			string adfn = path2 + afpn;
			if (repo2.FolderExists(adfn))
			{
				foreach (string newFile in repo2.GetFolderItems(adfn, DifItemType.File))
				{
					if (!processedFiles.Contains(Path.GetFileName(newFile)))
					{
						string nodename = "FileAdded";
						result = true;
						string fpn = Path.GetFileName(newFile);
						if (newFile.StartsWith(path2))
							fpn = newFile.Substring(path2.Length);
						if (newFile.StartsWith(path1))
							fpn = newFile.Substring(path1.Length);

						nodeIndex = AddFileNode(ResultsNode, nodeIndex, fpn, nodename);
					}
				}
			}
			List<string> processedDirs = new List<string>();
			foreach (string fn in repo1.GetFolderItems(path,DifItemType.Folder)) //something here to exlude .svn files
			{
				string folder = Path.GetDirectoryName(fn);

				if (connection.Settings.IsFolderPathIncluded(fn))
				{
					string fpn = fn.Substring(path1.Length);
					string dfn = path2 + fpn;
					string nodename;

					if (repo2.FolderExists(dfn))
					{
						nodename = "DirDiff";
					}
					else
					{
						nodename = "DirMissing";
						result = true;
					}

					XmlNode nextnode = ResultsNode.AppendChild(ResultsNode.OwnerDocument.CreateElement("DiffPath"));
					bool diff = RecurseDiff(fn, nextnode, useIgnoreList, ref nodeIndex, ref cancel);
					if (nodename == "DirDiff" && !diff)
						nodename = "DirSame";
					else if (repo2.LastItemHasPotentialConflict)
						nodename = "DirConflict";
					nodeIndex = AddDirectoryNode(ResultsNode, nextnode, nodeIndex, fn, folder, nodename);
					if (cancel)
						return result;
				}
				 processedDirs.Add(Path.GetFileName(fn));
			}
			if (repo2.FolderExists(adfn))
			{
				foreach (string newDir in repo2.GetFolderItems(adfn,DifItemType.Folder))
				{
					if (!processedDirs.Contains(Path.GetFileName(newDir)) && connection.Settings.IsFolderPathIncluded(newDir))
					{
						string nodename = "DirAdded";
						result = true;
						XmlNode nextnode = ResultsNode.AppendChild(ResultsNode.OwnerDocument.CreateElement("DiffPath"));
						nodeIndex = AddDirectoryNode(ResultsNode, nextnode, nodeIndex, newDir, Path.GetDirectoryName(newDir), nodename);
					}
				}
			}

			return result;
		}

		private int AddDirectoryNode(XmlNode ResultsNode, XmlNode nextnode, int nodeIndex, string fn, string folder, string nodename)
		{
			XmlAttribute fna = ResultsNode.OwnerDocument.CreateAttribute("DirName");
			fna.Value = Path.GetFileName(fn);
			nextnode.Attributes.Append(fna);
			fna = ResultsNode.OwnerDocument.CreateAttribute("DirFullName");
			fna.Value = fn;
			nextnode.Attributes.Append(fna);
			fna = ResultsNode.OwnerDocument.CreateAttribute("NodeIndex");
			fna.Value = (nodeIndex++).ToString();
			nextnode.Attributes.Append(fna);
			fna = ResultsNode.OwnerDocument.CreateAttribute("DirDiff");
			fna.Value = nodename;
			nextnode.Attributes.Append(fna);
			return nodeIndex;
		}

		private static int AddFileNode(XmlNode ResultsNode, int NodeIndex, string fpn, string nodename)
		{
			string fn = Path.GetFileName(fpn);
			XmlNode fd = ResultsNode.OwnerDocument.CreateElement(nodename);
			XmlAttribute fna = ResultsNode.OwnerDocument.CreateAttribute("FileName");
			fna.Value = fn;
			fd.Attributes.Append(fna);
			fna = ResultsNode.OwnerDocument.CreateAttribute("FileFullName");
			fna.Value = fpn;
			fd.Attributes.Append(fna);
			fna = ResultsNode.OwnerDocument.CreateAttribute("NodeIndex");
			fna.Value = (NodeIndex++).ToString();
			fd.Attributes.Append(fna);
			ResultsNode.AppendChild(fd);
			return NodeIndex;
		}

		public XmlDocument Results
		{
			get { return result; }
		}

		public ArrayList Errors
		{
			get { return errors; }
		}

	}
}